﻿#region Copyright (c) 2000-2013 Developer Express Inc.
/*
{*******************************************************************}
{                                                                   }
{       Developer Express .NET Component Library                    }
{                                                                   }
{                                                                   }
{       Copyright (c) 2000-2013 Developer Express Inc.              }
{       ALL RIGHTS RESERVED                                         }
{                                                                   }
{   The entire contents of this file is protected by U.S. and       }
{   International Copyright Laws. Unauthorized reproduction,        }
{   reverse-engineering, and distribution of all or any portion of  }
{   the code contained in this file is strictly prohibited and may  }
{   result in severe civil and criminal penalties and will be       }
{   prosecuted to the maximum extent possible under the law.        }
{                                                                   }
{   RESTRICTIONS                                                    }
{                                                                   }
{   THIS SOURCE CODE AND ALL RESULTING INTERMEDIATE FILES           }
{   ARE CONFIDENTIAL AND PROPRIETARY TRADE                          }
{   SECRETS OF DEVELOPER EXPRESS INC. THE REGISTERED DEVELOPER IS   }
{   LICENSED TO DISTRIBUTE THE PRODUCT AND ALL ACCOMPANYING .NET    }
{   CONTROLS AS PART OF AN EXECUTABLE PROGRAM ONLY.                 }
{                                                                   }
{   THE SOURCE CODE CONTAINED WITHIN THIS FILE AND ALL RELATED      }
{   FILES OR ANY PORTION OF ITS CONTENTS SHALL AT NO TIME BE        }
{   COPIED, TRANSFERRED, SOLD, DISTRIBUTED, OR OTHERWISE MADE       }
{   AVAILABLE TO OTHER INDIVIDUALS WITHOUT EXPRESS WRITTEN CONSENT  }
{   AND PERMISSION FROM DEVELOPER EXPRESS INC.                      }
{                                                                   }
{   CONSULT THE END USER LICENSE AGREEMENT FOR INFORMATION ON       }
{   ADDITIONAL RESTRICTIONS.                                        }
{                                                                   }
{*******************************************************************}
*/
#endregion Copyright (c) 2000-2013 Developer Express Inc.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using DevExpress.Xpf.Core;
#if DEMO
using DevExpress.Internal.DXWindow;
#endif
#if SL
using DevExpress.Xpf.Core.WPFCompatibility;
#endif
namespace UColor.WPF.Helpers {
	public class CodeViewControl : Control {
		#region Dependency Properties
		public static readonly DependencyProperty CodeTextProperty;
		public static readonly DependencyProperty CodeLanguageProperty;
		static CodeViewControl() {
			Type ownerType = typeof(CodeViewControl);
			CodeTextProperty = DependencyProperty.Register("CodeText", typeof(string), ownerType, new PropertyMetadata(string.Empty,
				(d, e) => ((CodeViewControl)d).RaiseCodeTextChanged(e)));
			CodeLanguageProperty = DependencyProperty.Register("CodeLanguage", typeof(CodeLanguage), ownerType, new PropertyMetadata(CodeLanguage.Plain,
				(d, e) => ((CodeViewControl)d).RaiseCodeLanguageChanged(e)));
		}
		#endregion
		RichTextBox codePresenter;
		DXExpander searchExpander;
		FrameworkElement searchPanel;
		TextBox textInput;
		Button searchButton;
		string currTextToSearch;
		TextPointer currPosition;
		Canvas selectionCanvas;
		Rectangle selectionRectangle;
		TextSelection codeSelection;
		ScrollViewer scrollViewer;
		TextBlock notFoundText;
		TextBlock noMoreFoundText;
		bool isSearchExpanded = false;
		public CodeViewControl() {
			DefaultStyleKey = typeof(CodeViewControl);
			Loaded += OnLoaded;
			Unloaded += OnUnloaded;
			SizeChanged += OnSizeChanged;
		}
		void OnUnloaded(object sender, RoutedEventArgs e) {
			if(Application.Current == null) return;
#if SL
			Application.Current.RootVisual.Focus();
#else
			if(Application.Current.MainWindow == null) return;
			Application.Current.MainWindow.Focus();
			FocusManager.SetFocusedElement(Application.Current.MainWindow, Application.Current.MainWindow);
#endif
		}
		void OnLoaded(object sender, RoutedEventArgs e) {
			Focus();
		}
		public string CodeText { get { return (string)GetValue(CodeTextProperty); } set { SetValue(CodeTextProperty, value); } }
		public Storyboard ExpandSearchPanelStoryboard { get; set; }
		public CodeLanguage CodeLanguage { get { return (CodeLanguage)GetValue(CodeLanguageProperty); } set { SetValue(CodeLanguageProperty, value); } }
		public string GetDisplayedText() { return RichTextBoxHelper.GetText(codePresenter); }
		public bool IsTemplateApplied { get { return codePresenter != null; } }
		protected bool HasSelection { get { return selectionRectangle != null && selectionRectangle.IsInVisualTree(); } }
		public bool IsSearchExpanded { get { return isSearchExpanded; } set { isSearchExpanded = value; UpdateSearchExpanded(); } }
		void UpdateSearchExpanded() {
			if(searchExpander != null)
				searchExpander.IsExpanded = IsSearchExpanded;
		}
		public void EnableTextSearch() {
			notFoundText.Visibility = Visibility.Collapsed;
			noMoreFoundText.Visibility = Visibility.Collapsed;
			if(textInput != null) {
				textInput.Focus();
				string codePresenterSelectionText = codePresenter.Selection.Text;
				int stringEndIndex = codePresenterSelectionText.IndexOf('\n');
				textInput.Text = stringEndIndex > 0 ? codePresenterSelectionText.Substring(0, stringEndIndex - 1) : codePresenterSelectionText;
			}
		}
		public void CancelSearch() {
			if(searchExpander == null) return;
			ClearSelection();
			ResetCurrentPosition();
			if(Visibility == Visibility.Visible)
				FocusCodePresenter();
		}
		public void FocusCodePresenter() {
			if(codePresenter != null) codePresenter.Focus();
		}
		public override void OnApplyTemplate() {
			base.OnApplyTemplate();
			searchExpander = (DXExpander)GetTemplateChild("SearchExpander");
			searchPanel = (FrameworkElement)GetTemplateChild("SearchPanel");
			selectionCanvas = (Canvas)GetTemplateChild("SelectionCanvas");
			scrollViewer = (ScrollViewer)GetTemplateChild("RichTextBoxScrollViewer");
			notFoundText = (TextBlock)GetTemplateChild("NotFoundText");
			noMoreFoundText = (TextBlock)GetTemplateChild("NoMoreFoundText");
			searchExpander.IsExpanded = IsSearchExpanded;
			GetAndSubscribeToCodePresenter();
			GetAndSubscribeToSearchButton();
			GetAndSubscribeToTextInput();
		}
		void OnSizeChanged(object sender, EventArgs e) {
#if SL
			SizeChanged -= OnSizeChanged;
			FocusCodePresenter();
#else
			UpdateExtentWidth();
#endif
		}
		void GetAndSubscribeToTextInput() {
			textInput = (TextBox)GetTemplateChild("TextInput");
			if(textInput != null) {
				textInput.KeyDown += OnTextInputKeyDown;
				textInput.TextChanged += OnTextInputTextChanged;
			}
		}
		void GetAndSubscribeToSearchButton() {
			searchButton = (Button)GetTemplateChild("SearchButton");
			if(searchButton != null) {
				searchButton.Click += (s, e) => Search();
			}
		}
		void GetAndSubscribeToCodePresenter() {
			codePresenter = (RichTextBox)GetTemplateChild("CodePresenter");
			if(codePresenter != null) {
				codePresenter.GotFocus += (s, e) => CancelSearch();
				codePresenter.AddHandler(RichTextBox.KeyDownEvent, (KeyEventHandler)OnKeyDown, true);
#if !SL
				codePresenter.Loaded += OnCodePresenterLoaded;
#endif
				UpdateCodePresenter();
			}
		}
		void OnKeyDown(object sender, KeyEventArgs e) {
			double scrollDiff = 15.0;
			switch(e.Key) {
				case Key.Up:
					scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - scrollDiff);
					break;
				case Key.Down:
					scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + scrollDiff);
					break;
				case Key.Left:
					scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset - scrollDiff);
					break;
				case Key.Right:
					scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset + scrollDiff);
					break;
				case Key.Home:
#if !SL
					scrollViewer.ScrollToHome();
#else
					scrollViewer.ScrollToVerticalOffset(.0);
#endif
					break;
				case Key.End:
#if !SL
					scrollViewer.ScrollToEnd();
#else
					scrollViewer.ScrollToVerticalOffset(scrollViewer.ExtentHeight);
#endif
					break;
				case Key.PageUp:
#if !SL
					scrollViewer.PageUp();
#else
					scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - scrollViewer.ActualHeight);
#endif
					break;
				case Key.PageDown:
#if !SL
					scrollViewer.PageDown();
#else
					scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + scrollViewer.ActualHeight);
#endif
					break;
			}
		}
		void Search() {
			if(textInput == null || string.IsNullOrEmpty(textInput.Text)) return;
			HideNotFoundTexts();
			UpdateNotFoundTextsVisibility(Search(textInput.Text));
			textInput.Focus();
		}
		internal string Search(string inputString) {
			if(string.IsNullOrEmpty(inputString)) return string.Empty;
			currTextToSearch = inputString.ToLowerInvariant();
			if(currPosition == null) {
				ResetCurrentPosition();
			}
			FindSelection(currPosition, true);
			if(codeSelection.Text.Length > 0) {
				codePresenter.Selection.Select(codeSelection.Start, codeSelection.End);
				currPosition = codePresenter.Selection.End;
			} else {
				ResetCurrentPosition();
			}
			Highlight(codePresenter.Selection);
			return codeSelection.Text;
		}
		void FindSelection(TextPointer startPoint, bool precalculatePosition) {
			RichTextBoxHelper.SearchText(startPoint, codePresenter, currTextToSearch);
		}
		bool AreEqual(string str, ref TextPointer ptr) {
			TextPointer nextPtr = ptr;
			TextPointer prevPtr;
			for(int i = 0; i < str.Length; ++i) {
				prevPtr = nextPtr;
				nextPtr = nextPtr.GetNextInsertionPosition(LogicalDirection.Forward);
				if(nextPtr == null) {
					codeSelection.Select(prevPtr, prevPtr);
					return false;
				}
				codeSelection.Select(prevPtr, nextPtr);
				if(string.IsNullOrEmpty(codeSelection.Text) || str[i] != codeSelection.Text.ToLowerInvariant()[0]) {
					ptr = nextPtr;
					return false;
				}
			}
			ptr = nextPtr;
			return true;
		}
		void ClearSelection() {
			if(selectionRectangle != null && selectionCanvas != null) {
				selectionRectangle.RemoveFromVisualTree();
			}
		}
		void ResetCurrentPosition() {
#if SL
			currPosition = codePresenter.ContentStart;
#else
			currPosition = codePresenter.Document.ContentStart;
#endif
		}
		void Highlight(TextSelection range) {
			if(string.IsNullOrEmpty(range.Text) || string.IsNullOrEmpty(currTextToSearch)) return;
			ClearSelection();
			Rect StartRect = range.Start.GetCharacterRect(LogicalDirection.Forward);
			StartRect.Union(range.End.GetCharacterRect(LogicalDirection.Forward));
			double horizontalOffset = StartRect.Left + StartRect.Width / 2 + Padding.Left - scrollViewer.ViewportWidth / 2;
			double verticalOffset = StartRect.Top + StartRect.Height / 2 + Padding.Top - scrollViewer.ViewportHeight / 2;
			scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset < StartRect.Left ? Math.Max(scrollViewer.HorizontalOffset, horizontalOffset) : Math.Min(scrollViewer.HorizontalOffset, horizontalOffset));
			scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset < StartRect.Top ? Math.Max(scrollViewer.VerticalOffset, verticalOffset) : Math.Min(scrollViewer.VerticalOffset, verticalOffset));
			if(selectionCanvas != null) {
				selectionCanvas.Children.Add(CreateHighlightRectangle(StartRect));
			}
		}
		Rectangle CreateHighlightRectangle(Rect bounds) {
			if(selectionRectangle == null) {
				selectionRectangle = new Rectangle();
				selectionRectangle.Fill = new SolidColorBrush(Color.FromArgb(75, 0, 0, 200));
				selectionRectangle.Stroke = new SolidColorBrush(Color.FromArgb(230, 0, 0, 254));
				selectionRectangle.StrokeThickness = 1;
				Grid.SetRowSpan(selectionRectangle, 2);
			}
			selectionRectangle.Width = bounds.Width + 2;
			selectionRectangle.Height = bounds.Height;
			Canvas.SetLeft(selectionRectangle, bounds.Left - 1);
			Canvas.SetTop(selectionRectangle, bounds.Top - 1);
			return selectionRectangle;
		}
		void HideNotFoundTexts() {
			noMoreFoundText.Visibility = Visibility.Collapsed;
			notFoundText.Visibility = Visibility.Collapsed;
		}
		void ScrollToStartPosition() {
#if !SL
			scrollViewer.ScrollToHome();
			scrollViewer.ScrollToLeftEnd();
#else
#endif
		}
		void OnTextInputTextChanged(object sender, TextChangedEventArgs e) {
			TextBox textInput = (TextBox)sender;
			string newValue = textInput.Text;
			ResetCurrentPosition();
			ClearSelection();
			if(newValue != null)
				currTextToSearch = newValue.ToLowerInvariant();
			if(!string.IsNullOrEmpty(currTextToSearch))
				Search();
		}
		void RaiseCodeTextChanged(DependencyPropertyChangedEventArgs e) {
			UpdateCodePresenter();
		}
		void RaiseCodeLanguageChanged(DependencyPropertyChangedEventArgs e) {
			UpdateCodePresenter();
		}
		void UpdateCodePresenter() {
			if(codePresenter == null) return;
			if(string.IsNullOrEmpty(CodeText))
				RichTextBoxHelper.ClearText(codePresenter);
			else
				RichTextBoxHelper.SetTextToRichTextBox(codePresenter, CodeText, CodeLanguage);
			ClearSelection();
			ResetCurrentPosition();
			currTextToSearch = "";
			if(textInput != null)
				textInput.Text = "";
			ScrollToStartPosition();
			HideNotFoundTexts();
#if !SL
			UpdateExtentWidth();
#endif
			codeSelection = codePresenter.Selection;
		}
#if !SL
		void OnCodePresenterLoaded(object sender, RoutedEventArgs e) {
			UpdateExtentWidth();
			FocusCodePresenter();
		}
		void UpdateExtentWidth() {
			codePresenter.Width = Math.Max(codePresenter.ActualWidth, codePresenter.ExtentWidth);
		}
#endif
		void OnTextInputKeyDown(object sender, KeyEventArgs e) {
			if(e.Key == System.Windows.Input.Key.Enter) {
				Search();
			}
		}
		void UpdateNotFoundTextsVisibility(string searchResult) {
			if(string.IsNullOrEmpty(searchResult)) {
				if(HasSelection) {
					if(noMoreFoundText != null) noMoreFoundText.Visibility = Visibility.Visible;
				} else {
					if(notFoundText != null) notFoundText.Visibility = Visibility.Visible;
				}
			}
		}
	}
}
